#include "stdafx.h"

namespace comms{
void cLog_Warning(const char* mask,...);

//cMeshObj Decode
cMeshConatainer * cMeshObj::Decode(cData &Src) {
	if(Src.IsEof()) {
		return NULL;
	}
	cMeshConatainer *pMesh = new cMeshConatainer;
	pMesh->SetName(Src.GetFilePn());
	if(!DecodeObj(Src, *pMesh)) {
		delete pMesh;
		pMesh = NULL;
	}
	return pMesh;
} // cMeshObj::Decode

//-----------------------------------------------------------------------------
// cMeshObj::Encode
//-----------------------------------------------------------------------------
void cMeshObj::Encode(const cMeshConatainer &Mesh, cData *To) {
	cAssert(To);
	if(!To) {
		return;
	}
	To->WriteString("# Wavefront Obj file\n");
	To->WriteString(cStr::Format("# %s\n", Mesh.GetName().ToCharPtr()));
    To->WriteString(cStr::Format("# verts: %d uv-s: %d\n",Mesh.GetPositions().Count(),Mesh.GetTexCoords().Count()));

    const cList<cSurface>& mtls=Mesh.GetMaterials();
    const cList<cObject>& objs=Mesh.GetObjects();

    char dir	[_MAX_PATH];
	char drive	[_MAX_PATH];
	char file	[_MAX_PATH];
	char ext	[_MAX_PATH];
	_splitpath( To->GetFilePn(), drive, dir, file, ext );

	char mtl[_MAX_PATH];
    char NewName[_MAX_PATH];
    sprintf(NewName,"%s.mtl",file);

	sprintf(mtl,"%s%s%s.mtl",drive,dir,file);
    
    FILE* M=fopen(mtl,"w");
	if(M){
        for(int i=0;i<mtls.Count();i++){
            const cSurface& mt=mtls[i];
            char nname[_MAX_PATH];
            strcpy(nname,(const char*)mt.Tex[0].FileName);
            int L=strlen(nname);
            for(int j=0;j<L;j++){
				//if(nname[j]=='\\'){
				//	memmove(nname+j+1,nname+j,L-j+1);
				//	nname[j+1]='\\';
				//	L++;
				//	j++;
				//}
            }
            fprintf(M,"newmtl %s\nNs 100.000\nd 1.00000\nillum 2\nKd 1.00000 1.00000 1.00000\nKa 0.00000 0.00000 0.00000\nKs 1.00000 1.00000 1.00000\nKe 0.00000e+0 0.00000e+0 0.00000e+0\nmap_Kd %s\n\n",(const char*)mt.Name,nname);
        }        
    	fclose(M);
	}	

    To->WriteString(cStr::Format("mtllib %s\n",NewName));

	cList<int> pos_Obj;
	cList<int> n_Obj;
	cList<int> uv_Obj;	

	pos_Obj.Add(10000000,Mesh.GetPositions().Count());
	n_Obj.Add(10000000,Mesh.GetNormals().Count());
	uv_Obj.Add(10000000,Mesh.GetTexCoords().Count());

	bool NoTexCoords = Mesh.GetTexCoords().Count() < 1;
	bool NoNormals = Mesh.GetNormals().Count() < 1;
	int index=0;
	
	while(index < Mesh.GetRaw().Count()) {        
		const cVec3i &Info = Mesh.GetRaw()[index++];        
		int nDeg = Info[0];
		const int idMtl = Info[1];
        const int idObj = Info[2];
		while(nDeg--) {
			const cVec3i i0 = Mesh.GetRaw()[index++];
			if(pos_Obj[i0[0]]>idObj)pos_Obj[i0[0]]=idObj;
			if(i0[1]>=0 && !NoTexCoords){
				if(uv_Obj[i0[1]]>idObj)uv_Obj[i0[1]]=idObj;				
			}
			if(!NoNormals) {
				if(i0[2]>=0){
					if(n_Obj[i0[2]]>idObj)n_Obj[i0[2]]=idObj;
				}else NoNormals=true;
			}
		}
	}	
	// Raw:
	index = 0;
	cStr Face;
	int NFaces = 0;	
    int curmtl=-1;
    int curobj=-1;

	int np=Mesh.GetPositions().Count();
	int nn=Mesh.GetNormals().Count();
	int nuv=Mesh.GetTexCoords().Count();	

	cList<int> posIds;
	posIds.Add(0,np);
	cList<int> nIds;
	nIds.Add(0,nn);
	cList<int> uvIds;
	uvIds.Add(0,nuv);	

	int cnv=0;
	int cnn=0;
	int cnuv=0;
	const cList<cVec3>& poss=Mesh.GetPositions();
	const cList<cVec2>& uvs=Mesh.GetTexCoords();
	const cList<cVec3>& ns=Mesh.GetNormals();		

	for(int curobj=0;curobj<Mesh.GetObjects().Count();curobj++){		
		To->WriteString(cStr::Format("\ng %s\n",(const char*)objs[curobj].Name));
		curmtl=-1;
		index=0;
		// Positions:
		int nc=0;
		for(int i=0;i<np;i++)if(pos_Obj[i]==curobj){
			const cVec3* It=&poss[i];
			To->WriteString(cStr::Format("\r\nv %f %f %f", It->x, It->y, It->z));
			posIds[i]=cnv++;
			nc++;
		}
		To->WriteString(cStr::Format("\r\n# %d positions\r\n", nc));

		// TexCoords:
		nc=0;		
		for(int i=0;i<nuv;i++)if(uv_Obj[i]==curobj){			
			const cVec2* It=&uvs[i];			
			To->WriteString(cStr::Format("\r\nvt %f %f", It->x, It->y));
			uvIds[i]=cnuv++;
			nc++;			
		}		
		To->WriteString(cStr::Format("\r\n# %d texture coordinates\r\n", nc));

		// Normals:
		nc=0;
		for(int i=0;i<nn;i++)if(n_Obj[i]==curobj){
			const cVec3* It=&ns[i];
			To->WriteString(cStr::Format("\r\nvn %f %f %f", It->x, It->y, It->z));
			nIds[i]=cnn++;
			nc++;
		}
		To->WriteString(cStr::Format("\r\n# %d normals\r\n", nc));		
		while(index < Mesh.GetRaw().Count()) {
			const cVec3i &Info = Mesh.GetRaw()[index++];        
			int nDeg = Info[0];
			const int idMtl = Info[1]&0xFFFF;
			const int idObj = Info[2];
			if(curobj==idObj){
				if(curmtl!=idMtl){
					curmtl=idMtl;
					if(curmtl!=-1){
						To->WriteString(cStr::Format("\nusemtl %s\n",(const char*)mtls[curmtl].Name));                
					}
				}
				Face = "f";		
				while(nDeg--) {
					const cVec3i i0 = Mesh.GetRaw()[index++];
					if(NoTexCoords && NoNormals) {
						Face << cStr::Format(" %d", posIds[i0[0]] + 1); // Only position.
					} else if(NoTexCoords && !NoNormals) {
						Face << cStr::Format(" %d//%d", posIds[i0[0]] + 1, nIds[i0[2]] + 1); // Position && Normal.
					} else if(!NoTexCoords && NoNormals) {
						Face << cStr::Format(" %d/%d", posIds[i0[0]] + 1, uvIds[i0[1]] + 1); // Position && TexCoord.
					} else {
						Face << cStr::Format(" %d/%d/%d", posIds[i0[0]] + 1, uvIds[i0[1]] + 1, nIds[i0[2]] + 1); // Position, TexCoord, and Normal.
					}
				}
				Face << "\n";
				To->WriteString(Face);
				NFaces++;
			}else index+=nDeg;
		}				
	}
	To->WriteString(cStr::Format("\r\n# %d faces\r\n", NFaces));
	cList<OneMorph*> Morphs1;
	Morphs1.Copy(Mesh.Morphs);
	((cMeshConatainer&)Mesh).Morphs.Clear();
	for(int i=0;i<Morphs1.Count();i++){
		Morphs1[i]->Pos.Count();
		for(int j=0;j<nn;j++){
			((cMeshConatainer&)Mesh).GetPositions()[j]+=Morphs1[i]->Pos[j];
		}
		char cc[512];
		strcpy(cc,To->GetFilePn());
		if(strlen(cc)>5)cc[strlen(cc)-4]=0;
		strcat(cc,"_");
		strcat(cc,Mesh.Morphs[i]->Name);
		strcat(cc,".obj");
		cData D1;
		D1.SetFilePn(cc,true);
		Encode(Mesh,&D1);		
		//cIO::SaveFile(cc,D1);
		for(int j=0;j<nn;j++){
			((cMeshConatainer&)Mesh).GetPositions()[j]-=Morphs1[i]->Pos[j];
		}
		delete(Morphs1[i]);
	}
} // cMeshObj::Encode
bool GetTextureFromMtl(const char* obj_name,const char* MtlLibName,const char* MtlName,char* TexName,bool FullName){
	char dir	[_MAX_PATH];
	char drive	[_MAX_PATH];
	char file	[_MAX_PATH];
	char ext	[_MAX_PATH];
	_splitpath( obj_name, drive, dir, file, ext );
	char newn	[_MAX_PATH];
	sprintf(newn,"%s%s%s",drive,dir,MtlLibName);
	FILE* F=fopen(newn,"rb");
	if(F){
		fseek(F,0,SEEK_END);
		int sz=ftell(F);
		fseek(F,0,SEEK_SET);
		char* s=new char[sz+1];
		s[sz]=0;
		fread(s,sz,1,F);
		fclose(F);
		char mn[256];
		sprintf(mn,"newmtl %s",MtlName);
		char* s2=strstr(s,mn);
		if(s2){
			s2+=strlen(mn);
			s2=strstr(s2,"map_Kd ");
			if(s2){
				s2+=7;
				char* s3=strchr(s2,0x0D);
				if(!s3)s3=strchr(s2,0x0A);
				if(s3){
					int L=s3-s2;
					if(FullName){
						sprintf(TexName,"%s%s",drive,dir);
					}else TexName[0]=0;
					int L1=strlen(TexName);
					memcpy(TexName+L1,s2,L);
					TexName[L+L1]=0;
					return true;
				}
			}
		}
	}
	return false;
}
class hVec3:public cVec3{//3 floats-s	
public:
	hVec3(){};
	hVec3(float a,float b,float c){
		x=a;
		y=b;
		z=c;
	}
	operator DWORD(){
		return *((DWORD*)&x)+*((DWORD*)&y)+*((DWORD*)&z);
	}	
	bool   operator == (const hVec3& c) const{
		return c.x==x && c.y==y && c.z==z;
	}
};
//-----------------------------------------------------------------------------
// cMeshObj::DecodeObj
//-----------------------------------------------------------------------------
bool cMeshObj::DecodeObj(cData &Src, cMeshConatainer &Mesh) {	
	int idCurMtl = 0;
    int CurObject=-1;
	uni_hash<DWORD,hVec3,800000,8192> PosHash;
	cList<DWORD> PosID;

	cStr MaterialFileName;
	int pp=0;
	while(true) {
		if(Src.Size()>8000000){
			if(Src.GetPos()-pp>2000000){
				//ShowGlobalProgress("READING_OBJ",Src.GetPos()>>8,Src.Size()>>8);
				pp=Src.GetPos();
			}
		}
		const int r = LoadLine(Src);
		if(-1 == r) { // EOF
			break;
		}
		if(0 == r) { // Comment or empty string.
			continue;
		}
        if(cStr::Equals(m_Tokens[0], "g") && m_Tokens.Count()>1) {
			PosHash.reset();
            bool IsFound = false;
            for(int idObj = 0; idObj < Mesh.GetObjects().Count(); idObj++) {
				if(cStr::Equals(Mesh.GetObjects()[idObj].Name, m_Tokens[1])) {
					IsFound = true;
					CurObject = idObj;
					break;
				}
			}
            if(!IsFound) {
                cObject obj;
                CurObject=Mesh.GetObjects().Count();
                Mesh.GetObjects().Add(obj);
                Mesh.GetObjects().GetLast().Name=m_Tokens[1];
            }
        }else
		if(cStr::Equals(m_Tokens[0], "mtllib")) {
			MaterialFileName = m_Tokens[1];
		} else if(cStr::Equals(m_Tokens[0], "v")) { // Position:
			if(m_Tokens.Count() < 4) {
				cLog_Warning("cMeshObj::DecodeObj(): Too few position coordinates: \"%s\".", m_Buffer.ToCharPtr());
				return false;
			}
			cVec3 Position;
			//cStr::ToFloat(m_Tokens[1],&Position.x);
			Position.x=atof(m_Tokens[1]);
			//cStr::ToFloat(m_Tokens[2],&Position.y);
			Position.y=atof(m_Tokens[2]);
			//cStr::ToFloat(m_Tokens[3],&Position.z);
			Position.z=atof(m_Tokens[3]);
			hVec3 v2(Position.x,Position.y,Position.z);
			DWORD* id=PosHash.get(v2);
			if(id==NULL){				
				DWORD p=Mesh.GetPositions().Add(Position);
				PosID.Add(p);
				PosHash.add(v2,p);								
			}else{
				PosID.Add(*id);
			}			
		} else if(cStr::Equals(m_Tokens[0], "vt")) { // TexCoord:
			if(m_Tokens.Count() < 3) {
				cLog_Warning("cMeshObj::DecodeObj(): Too few texture coordinates: \"%s\".", m_Buffer.ToCharPtr());
				return false;
			}
			cVec2 TexCoord;
			//cStr::ToFloat(m_Tokens[1],&TexCoord.x);
			TexCoord.x=atof(m_Tokens[1]);
			//cStr::ToFloat(m_Tokens[2],&TexCoord.y);
			TexCoord.y=atof(m_Tokens[2]);
			Mesh.GetTexCoords().Add(TexCoord);
		} else if(cStr::Equals(m_Tokens[0], "vn")) { // Normal:
			if(m_Tokens.Count() < 4) {
				cLog_Warning("cMeshObj::DecodeObj(): Too few normal coordinates: \"%s\".", m_Buffer.ToCharPtr());
				return false;
			}
			cVec3 Normal;
			Normal.x=atof(m_Tokens[1]);
			Normal.y=atof(m_Tokens[2]);
			Normal.z=atof(m_Tokens[3]);			
			Mesh.GetNormals().Add(Normal);
		} else if(cStr::Equals(m_Tokens[0], "f")) { // Face:
			//if(m_Tokens.Count() < 4) {
			//	cLog::Warning("cMeshObj::DecodeObj(): Too few face indices: \"%s\".", m_Buffer.ToCharPtr());
			//	return false;
			//}
			if(m_Tokens.Count() >=4) {
				if(CurObject==-1) {
					cObject obj;
					CurObject=0;
					Mesh.GetObjects().Add(obj);
					Mesh.GetObjects().GetLast().Name="default";
				}
				if(!AddFace(Mesh, idCurMtl, CurObject)) {
					return false;
				}
			}
		} else if(cStr::Equals(m_Tokens[0], "usemtl")) { // Material:
			if(m_Tokens.Count() > 1) { // There is a name token.
				// If raw is empty, usemtl was listed before any faces.
				// So previous default material is not needed at all.
				/*
				if(Mesh.GetRaw().IsEmpty()) {
					Mesh.GetMaterials().Clear();
					Mesh.GetMaterials().Add(cSurface::Brass);
					Mesh.GetMaterials().GetLast().Name = m_Tokens[1];
					idCurMtl = 0;
				} else { // There are faces, that surely use previous materials.
				*/
					bool IsFound = false;
					for(int idMtl = 0; idMtl < Mesh.GetMaterials().Count(); idMtl++) {
						if(cStr::Equals(Mesh.GetMaterials()[idMtl].Name, m_Tokens[1])) {
							IsFound = true;
							idCurMtl = idMtl;
							break;
						}
					}
					if(!IsFound) {
						char TexName[_MAX_PATH]="";
						GetTextureFromMtl(Src.GetFilePn(),MaterialFileName,m_Tokens[1],TexName,true);						
						idCurMtl = Mesh.GetMaterials().Count();
                        cSurface m;
						Mesh.GetMaterials().Add(m);
						Mesh.GetMaterials().GetLast().Name = m_Tokens[1];
						Mesh.GetMaterials().GetLast().Tex[0].FileName=TexName;
					}
				//}
			}
		}
	}
    // Default material:
	cList<cVec3i>& raw=Mesh.GetRaw();
	for(int i=0;i<raw.Count();i++){
		int n=raw[i][0];
		int j;
		for(j=0;j<n;j++){
			raw[i+j+1][0]=PosID[raw[i+j+1][0]];
		}
		i+=j;
	}
    if(Mesh.GetMaterials().Count()==0){
        cSurface m;
	    Mesh.GetMaterials().Add(m);
	    Mesh.GetMaterials().GetLast().Name = "default";
    }
	return true;
} // cMeshObj::DecodeObj

//-----------------------------------------------------------------------------
// cMeshObj::LoadLine
//-----------------------------------------------------------------------------
int cMeshObj::LoadLine(cData &Src) {
	if(!Src.ReadString(&m_Buffer)) {
		return -1; // EOF
	}
	// Parsing:
	static cStr Str;
	Str=m_Buffer;
	// Cut off comments:
	const int iSharp = Str.IndexOf('#');
	if(iSharp > -1) {
		//Str[iSharp] = '\0';
		Str.Remove(iSharp);
	}
	Str.Split(&m_Tokens, " \t");

	if(m_Tokens.Count() < 1) { // Comment or empty string.
		return 0;
	}

	return 1;
} // cMeshObj::LoadLine

//-----------------------------------------------------------------------------
// cMeshObj::GetIndices
//-----------------------------------------------------------------------------
const cVec3i cMeshObj::GetIndices(const cStr &Token) {
	static cStr Str;
	Str=Token;
	char *None = "-1";
	char *TexCoord = None;
	char *Normal = None;
	// Replace '/' with '\0' and setup TexCoord and Normal:
	for(char *c = Str.ToCharPtr(); *c != '\0'; c++) {
		if('/' == *c) {
			if(None == TexCoord) {
				TexCoord = c + 1;
			} else {
				Normal = c + 1;
			}
			*c = '\0';
		}
	}
	cVec3i R;
	R[0]=-1;
	R[1]=-1;
	R[2]=-1;
	R[0]=atoi(Str);
	if(TexCoord[0])R[1]=atoi(TexCoord);
	if(Normal[0])R[2]=atoi(Normal);
	//cStr::ToInt(Str,&R[0]);
	//cStr::ToInt(TexCoord,&R[1]);
	//cStr::ToInt(Normal,&R[2]);

	return R;
} // cMeshObj::GetIndices

//-------------------------------------------------------------------------------
// cMeshObj::AddFace
//-------------------------------------------------------------------------------
bool cMeshObj::AddFace(cMeshConatainer &Mesh, const int idMtl, const int idObj) {
	// Face Info:
	Mesh.GetRaw().Add(cVec3i(m_Tokens.Count() - 1, idMtl, idObj));
	// Indices:
	for(int i = 1; i < m_Tokens.Count(); i++) {
		cVec3i Index = GetIndices(m_Tokens[i]);
		// Position index:
		if(Index[0] > 0) {
			Index[0]--;
		} else if(Index[0] < 0) {
			Index[0] = Mesh.GetPositions().Count() + Index[0];
		} else {
			cLog_Warning("cMeshObj: Zero indices not allowed: \"%s\".", m_Buffer.ToCharPtr());
			return false;
		}
		// TexCoord index:
		if(Index[1] > 0) {
			Index[1]--;
		} else if(Index[1] < 0) {
			Index[1] = Mesh.GetTexCoords().Count() + Index[1];
		} else {
			// Zero index, i.e. no tex coord.
		}
		// Normal index:
		if(Index[2] > 0) {
			Index[2]--;
		} else if(Index[2] < 0) {
			Index[2] = Mesh.GetNormals().Count() + Index[2];
		} else {
			// Zero index, i.e. no normal.
		}
		Mesh.GetRaw().Add(Index);
	}
	return true;
} // cMeshObj::AddFace
}